﻿using System;
using System.Activities;
using System.Activities.DurableInstancing;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.DurableInstancing;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using YasharEl.Infrastructure.Diagnostics.Logging;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.Threading;
using YasharEl.Infrastructure.WorkflowModel.Configuration;

namespace YasharEl.Infrastructure.WorkflowModel.Management
{
    public class DefaultWorkflowManagementService : IWorkflowManagementService
    {
        #region Static Fields

        private static readonly XNamespace Workflow4Namespace = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties");

        private static readonly XNamespace Workflow45Namespace = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.5/properties");

        private static readonly XName WorkflowHostTypePropertyName = Workflow4Namespace.GetName("WorkflowHostType");

        private static readonly XName DefinitionIdentitiesName = Workflow45Namespace.GetName("DefinitionIdentities");

        private static readonly XName DefinitionIdentityName = Workflow45Namespace.GetName("DefinitionIdentity");

        private static readonly XName DefinitionIdentityFilterName = Workflow45Namespace.GetName("DefinitionIdentityFilter");

        private static readonly XName WorkflowApplicationName = Workflow45Namespace.GetName("WorkflowApplication");

        #endregion

        #region ReadOnly Fields

        private readonly IList<WorkerThread> workerThreads = new List<WorkerThread>();
        private readonly ILogger<DefaultWorkflowManagementService> logger;
        private readonly IDependencyResolver resolver;
        private readonly WorkflowModelConfig workflowModelConfig;
        private readonly SqlWorkflowInstanceStore workflowInstanceStore = null;

        #endregion

        #region Constructors

        public DefaultWorkflowManagementService(ILogger<DefaultWorkflowManagementService> logger, IDependencyResolver resolver, WorkflowModelConfig workflowModelConfig)
        {
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not be null.");
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");
            Contract.Requires<ArgumentNullException>(workflowModelConfig != null, "workflowModelConfig could not be null.");
            Contract.Requires<ArgumentNullException>(workflowModelConfig.InstanceStore != null, "workflowModelConfig.InstanceStore could not be null.");

            this.logger = logger;
            this.resolver = resolver;
            this.workflowModelConfig = workflowModelConfig;

            workflowInstanceStore = new SqlWorkflowInstanceStore(workflowModelConfig.InstanceStore.ConnectionString)
            {
                InstanceLockedExceptionAction = workflowModelConfig.InstanceStore.InstanceLockedExceptionAction,
                InstanceCompletionAction = workflowModelConfig.InstanceStore.InstanceCompletionAction,
                HostLockRenewalPeriod = workflowModelConfig.InstanceStore.HostLockRenewalPeriod,
                RunnableInstancesDetectionPeriod = workflowModelConfig.InstanceStore.RunnableInstancesDetectionPeriod
            };
        }

        #endregion

        #region IWorkflowManagementService Members

        public void Start()
        {
            AddWorkerThread()
                .Start();
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            lock (workerThreads)
                for (var i = 0; i < workerThreads.Count; i++)
                    workerThreads[i].Stop();
        }

        #endregion

        #region Private Methods

        private WorkerThread AddWorkerThread()
        {
            lock (workerThreads)
            {
                var result = new WorkerThread(
                    ResumePendingWorkflows,
                    (e) =>
                    {
                        if (e != null)
                        {
                            if (e.GetType() == typeof(TimeoutException))
                                return;

                            logger.Error(e, "Error occurred during pending workflows detection and resumption thread.");
                        }
                    }
                );

                workerThreads.Add(result);

                result.Stopped += delegate(object sender, EventArgs e)
                {
                    var wt = sender as WorkerThread;
                    lock (workerThreads)
                        workerThreads.Remove(wt);
                };

                return result;
            }
        }

        #endregion

        #region IWorkflowOwnershipManagementService Members

        public void TakeOwnership(WorkflowApplication workflowApplication)
        {
            logger.Info("Taking ownership...");
            workflowApplication.InstanceStore = workflowInstanceStore;
        }

        public void ReleaseOwnership(WorkflowApplication workflowApplication)
        {
            logger.Info("Releasing ownership for workflow {0}", workflowApplication.Id);
        }

        #endregion

        #region Worker Thread - resume pending workflows

        private void ResumePendingWorkflows()
        {
            bool hasRunnableWorkflows = false;
            InstanceHandle instanceHandle = null;
            try
            {
                logger.Info("Resuming pending wirkflows instance store ownership taking...");

                #region Take Ownership

                instanceHandle = workflowInstanceStore.CreateInstanceHandle();

                CreateWorkflowOwnerWithIdentityCommand createWorkflowOwnerCommand = new CreateWorkflowOwnerWithIdentityCommand();

                createWorkflowOwnerCommand.InstanceOwnerMetadata.Add(
                    WorkflowHostTypePropertyName,
                    new InstanceValue(WorkflowApplicationName)
                );
                createWorkflowOwnerCommand.InstanceOwnerMetadata.Add(
                    DefinitionIdentityFilterName,
                    new InstanceValue(WorkflowIdentityFilter.Any)
                );
                createWorkflowOwnerCommand.InstanceOwnerMetadata.Add(
                    DefinitionIdentitiesName,
                    new InstanceValue(new Collection<WorkflowIdentity>())
                );

                InstanceView view = workflowInstanceStore.Execute(
                    instanceHandle,
                    createWorkflowOwnerCommand,
                    TimeSpan.FromSeconds(5)
                );
                workflowInstanceStore.DefaultInstanceOwner = view.InstanceOwner;

                #endregion

                #region Wait For Events

                InstancePersistenceEvent foundEvent = null;
                foreach (InstancePersistenceEvent currentEvent in
                    workflowInstanceStore.WaitForEvents(instanceHandle, workflowModelConfig.InstanceStore.RunnableInstancesDetectionPeriod))
                {
                    if (currentEvent == HasRunnableWorkflowEvent.Value)
                    {
                        hasRunnableWorkflows = true;
                        foundEvent = currentEvent;
                        break;
                    }
                }

                #endregion

                #region Process Runnable Workflow

                if (hasRunnableWorkflows)
                {
                    logger.Debug("Found runnable workflows");

                    InstanceView loadRunnableWorkflowView = workflowInstanceStore.Execute(
                        instanceHandle,
                        new TryLoadRunnableWorkflowCommand(),
                        TimeSpan.FromSeconds(30)
                    );

                    if (loadRunnableWorkflowView != null)
                    {
                        logger.Debug("Trying to load runnable workflow instance : {0}", loadRunnableWorkflowView.InstanceId);

                        IWorkflowDefinitionLoader workflowDefinitionLoader = resolver.Resolve<IWorkflowDefinitionLoader>();
                        IDictionary<string, object> inputs = new Dictionary<string, object>();
                        Activity workflowActivity = workflowDefinitionLoader.LoadWorkflowDefinition(
                            loadRunnableWorkflowView.InstanceId,
                            out inputs
                        );

                        if (inputs == null)
                            inputs = new Dictionary<string, object>();

                        WorkflowIdentity workflowIdentity = null;
                        if (loadRunnableWorkflowView.InstanceMetadata != null &&
                            loadRunnableWorkflowView.InstanceMetadata.ContainsKey(DefinitionIdentityName))
                        {
                            workflowIdentity = loadRunnableWorkflowView.InstanceMetadata[DefinitionIdentityName].Value as WorkflowIdentity;
                        }

                        WorkflowApplication workflowApplication = null;
                        if (workflowIdentity != null)
                        {
                            workflowApplication = new WorkflowApplication(workflowActivity, /*inputs,*/ workflowIdentity);
                        }
                        else
                        {
                            workflowApplication = new WorkflowApplication(workflowActivity/*, inputs*/);
                        }

                        workflowApplication.SetupDefaults(resolver);

                        workflowApplication.InstanceStore = workflowInstanceStore;
                            //.SetupInstanceStore(resolver);

                        workflowApplication.Load(loadRunnableWorkflowView.InstanceId);

                        logger.Info("Workflow [0} loaded {0}...", workflowApplication.Id);

                        workflowApplication.Run();
                    }
                }

                #endregion

                #region Not Found Workflows

                else
                {
                    logger.Debug("Did not find runnable workflows");
                }

                #endregion
            }
            finally
            {
                if (workflowInstanceStore != null)
                {
                    workflowInstanceStore.Execute(instanceHandle, new DeleteWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
                    instanceHandle.Free();

                    logger.Debug("Released pending workflows instance store ownership...");
                }

                Thread.Sleep(5000);
            }
        }

        #endregion
    }
}
